using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using System.IO;

using System.Diagnostics;

namespace Toenda.Foundation.Windows.Forms {
	/// <summary>
	/// FolderTreeView
	/// </summary>
	public class FolderTreeView : System.Windows.Forms.TreeView {
		private System.Windows.Forms.ImageList folderTreeViewImageList;
		private System.Globalization.CultureInfo cultureInfo = System.Globalization.CultureInfo.CurrentCulture;

		#region Constructors

		public FolderTreeView()
		{
			this.BeforeExpand += new System.Windows.Forms.TreeViewCancelEventHandler(this.TreeViewBeforeExpand);
		}

		public void InitFolderTreeView()
		{
			InitImageList();
			ShellOperations.PopulateTree(this, base.ImageList);
			if(this.Nodes.Count > 0)
			{
				this.Nodes[0].Expand();
			}
		}

		private void InitImageList()
		{
			// setup the image list to hold the folder icons
			folderTreeViewImageList = new System.Windows.Forms.ImageList();
			folderTreeViewImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit;
			folderTreeViewImageList.ImageSize = new System.Drawing.Size(16, 16);
			folderTreeViewImageList.TransparentColor = System.Drawing.Color.Transparent;

			// add the Desktop icon to the image list
			try
			{
				folderTreeViewImageList.Images.Add(ExtractIcons.GetDesktopIcon());
			}
			catch
			{
				// Create a blank icon if the desktop icon fails for some reason
				Bitmap bmp = new Bitmap(16,16);
				Image img = (Image)bmp;
				folderTreeViewImageList.Images.Add((Image)img.Clone());
				bmp.Dispose();
			}
			this.ImageList = folderTreeViewImageList;
		}

		#endregion

		#region Event Handlers

		private void TreeViewBeforeExpand(object sender, System.Windows.Forms.TreeViewCancelEventArgs e)
		{
			this.BeginUpdate();
			ShellOperations.ExpandBranch(e.Node, this.ImageList);
			this.EndUpdate();
		}

		#endregion

		#region Furty.Windows.Forms.FolderTreeView Properties & Methods

		public string GetSelectedNodePath()
		{
			return ShellOperations.GetFilePath(SelectedNode);
		}

		public bool DrillToFolder(string folderPath)
		{
			bool folderFound = false;
			if(Directory.Exists(folderPath)) // don't bother drilling unless the directory exists
			{
				this.BeginUpdate();
				// if there's a trailing \ on the folderPath, remove it unless it's a drive letter
				if(folderPath.Length > 3 && folderPath.LastIndexOf("\\") == folderPath.Length -1)
					folderPath = folderPath.Substring(0, folderPath.Length -1);
				//Start drilling the tree
				DrillTree(this.Nodes[0].Nodes, folderPath.ToUpper(cultureInfo), ref folderFound);
				this.EndUpdate();
			}
			if(!folderFound)
				this.SelectedNode = this.Nodes[0];
			return folderFound;
		}

		public void DrillTree(TreeNodeCollection tnc, string path, ref bool folderFound)
		{
			foreach(TreeNode tn in tnc)
			{
				if(!folderFound)
				{
					this.SelectedNode = tn;
					string tnPath = ShellOperations.GetFilePath(tn).ToUpper(cultureInfo);
					if(path == tnPath && !folderFound)
					{
						this.SelectedNode = tn;
						tn.EnsureVisible();
						folderFound = true;
						break;
					}
					else if(path.IndexOf(tnPath) > -1 && !folderFound)
					{
						tn.Expand();
						DrillTree(tn.Nodes, path, ref folderFound);
					}
				}
			}
		}


		#endregion
        
		#region System.Windows.Forms.TreeView Properties

        public override System.Drawing.Color BackColor
        {
            get
            { return base.BackColor; }
            set
            { base.BackColor = value; }
        }
        
        public override System.Drawing.Image BackgroundImage
        {
            get
            { return base.BackgroundImage; }
            set
            { base.BackgroundImage = value; }
        }
        
        public override System.Drawing.Color ForeColor
        {
            get
            { return base.ForeColor; }
            set
            { base.ForeColor = value; }
        }
        
        public override string Text
        {
            get
            { return base.Text; }
            set
            { base.Text = value; }
        }
        
        public override bool AllowDrop
        {
            get
            { return base.AllowDrop; }
            set
            { base.AllowDrop = value; }
        }
        
        public override System.Windows.Forms.AnchorStyles Anchor
        {
            get
            { return base.Anchor; }
            set
            { base.Anchor = value; }
        }
        
        public override System.Windows.Forms.BindingContext BindingContext
        {
            get
            { return base.BindingContext; }
            set
            { base.BindingContext = value; }
        }
        
        public override System.Windows.Forms.ContextMenu ContextMenu
        {
            get
            { return base.ContextMenu; }
            set
            { base.ContextMenu = value; }
        }
        
        public override System.Windows.Forms.Cursor Cursor
        {
            get
            {  return base.Cursor; }
            set
            {  base.Cursor = value; }
        }
        
        public override System.Drawing.Rectangle DisplayRectangle
        {
            get
            { return base.DisplayRectangle; }
        }
        
        public override System.Windows.Forms.DockStyle Dock
        {
            get
            { return base.Dock;  }
            set
            { base.Dock = value; }
        }
        
        public override bool Focused
        {
            get
            { return base.Focused; }
        }
        
        public override System.Drawing.Font Font
        {
            get
            { return base.Font; }
            set
            { base.Font = value; }
        }
        
        public override System.Windows.Forms.RightToLeft RightToLeft
        {
            get
            { return base.RightToLeft; }
            set
            { base.RightToLeft = value; }
        }
        
        public override System.ComponentModel.ISite Site
        {
            get
            { return base.Site; }
            set
            { base.Site = value; }
        }

		#endregion

		#region System.Windows.Forms.TreeView Overrides
 
        public override void ResetText()
        {
            base.ResetText();
        }
        
        public override void Refresh()
        {
            base.Refresh();
        }
        
        public override void ResetRightToLeft()
        {
            base.ResetRightToLeft();
        }
        
        public override void ResetForeColor()
        {
            base.ResetForeColor();
        }
        
        public override void ResetFont()
        {
            base.ResetFont();
        }
        
        public override void ResetCursor()
        {
            base.ResetCursor();
        }
        
        public override void ResetBackColor()
        {
            base.ResetBackColor();
        }
        
        public override bool PreProcessMessage(ref System.Windows.Forms.Message msg)
        {
            return base.PreProcessMessage(ref msg);
        }
        
        public override System.Runtime.Remoting.ObjRef CreateObjRef(System.Type requestedType)
        {
            return base.CreateObjRef(requestedType);
        }
        
        public override object InitializeLifetimeService()
        {
            return base.InitializeLifetimeService();
        }
        
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }
        
        public override string ToString()
        {
            return base.ToString();
        }

		#endregion
    }

	#region ShellOperations Class

	public class ShellOperations
	{

		#region ShellFolder Enums
		// Enums for standard Windows shell folders
		public enum ShellFolder
		{
			Desktop = Shell32.ShellSpecialFolderConstants.ssfDESKTOP, 
			DesktopDirectory = Shell32.ShellSpecialFolderConstants.ssfDESKTOPDIRECTORY,
			MyComputer = Shell32.ShellSpecialFolderConstants.ssfDRIVES,
			MyDocuments = Shell32.ShellSpecialFolderConstants.ssfPERSONAL,
			MyPictures = Shell32.ShellSpecialFolderConstants.ssfMYPICTURES,
			History = Shell32.ShellSpecialFolderConstants.ssfHISTORY,
			Favorites = Shell32.ShellSpecialFolderConstants.ssfFAVORITES,
			Fonts = Shell32.ShellSpecialFolderConstants.ssfFONTS,
			ControlPanel = Shell32.ShellSpecialFolderConstants.ssfCONTROLS,
			TemporaryInternetFiles = Shell32.ShellSpecialFolderConstants.ssfINTERNETCACHE,
			MyNetworkPlaces = Shell32.ShellSpecialFolderConstants.ssfNETHOOD,
			NetworkNeighborhood = Shell32.ShellSpecialFolderConstants.ssfNETWORK,
			ProgramFiles = Shell32.ShellSpecialFolderConstants.ssfPROGRAMFILES,
			RecentFiles = Shell32.ShellSpecialFolderConstants.ssfRECENT,
			StartMenu = Shell32.ShellSpecialFolderConstants.ssfSTARTMENU,
			Windows = Shell32.ShellSpecialFolderConstants.ssfWINDOWS,
			Printers = Shell32.ShellSpecialFolderConstants.ssfPRINTERS,
			RecycleBin = Shell32.ShellSpecialFolderConstants.ssfBITBUCKET,
			Cookies = Shell32.ShellSpecialFolderConstants.ssfCOOKIES,
			ApplicationData = Shell32.ShellSpecialFolderConstants.ssfAPPDATA,
			SendTo = Shell32.ShellSpecialFolderConstants.ssfSENDTO,
			StartUp = Shell32.ShellSpecialFolderConstants.ssfSTARTUP
		}
		#endregion
		
		#region FolderTreeView Methods

		#region GetFilePath
		public static string GetFilePath(TreeNode tn)
		{
			try
			{
				Shell32.FolderItem folderItem = (Shell32.FolderItem)tn.Tag;
				string folderPath = folderItem.Path;
				if(Directory.Exists(folderPath))
					return folderPath;
				else
					return "";
			}
			catch
			{
				return "";
			}
		}
		#endregion

		#region Populate Tree
		public static void PopulateTree(TreeView tree, ImageList imageList)
		{
			int imageCount = imageList.Images.Count -1;
			tree.Nodes.Clear();
			AddRootNode(tree, ref imageCount, imageList, ShellFolder.Desktop, true);
			if(tree.Nodes.Count > 1)
			{
				tree.SelectedNode = tree.Nodes[1];
				ExpandBranch(tree.Nodes[1], imageList);
			}
		}
		#endregion

		#region Add Root Node
		private static void AddRootNode(TreeView tree, ref int imageCount, ImageList imageList, ShellFolder shellFolder, bool getIcons)
		{
			Shell32.Shell shell32 = new Shell32.ShellClass();
			Shell32.Folder shell32Folder = shell32.NameSpace(shellFolder);
			Shell32.FolderItems items = shell32Folder.Items();

			tree.Nodes.Clear();
			TreeNode desktop = new TreeNode("Desktop", 0, 0);
	
			// Added in version 1.11
			// add a FolderItem object to the root (Desktop) node tag that corresponds to the DesktopDirectory namespace
			// This ensures that the GetSelectedNodePath will return the actual Desktop folder path when queried.
			// There's possibly a better way to create a Shell32.FolderItem instance for this purpose, 
			// but I surely don't know it

			Shell32.Folder dfolder = shell32.NameSpace(ShellFolder.DesktopDirectory);
			foreach(Shell32.FolderItem fi in dfolder.ParentFolder.Items())
			{
				if(fi.Name == dfolder.Title)
				{
					desktop.Tag = fi;
					break;
				}
			}

			// Add the Desktop root node to the tree
			tree.Nodes.Add(desktop);
			
			// iterate through the Desktop namespace and populate the first level nodes
			foreach(Shell32.FolderItem item in items)
			{
				if(item.IsFolder) // this ensures that desktop shortcuts etc are not displayed
				{
					TreeNode tn = AddTreeNode(item, ref imageCount, imageList, getIcons);
					desktop.Nodes.Add(tn);
					CheckForSubDirs(tn, imageList);
				}
			}
			
		}
		#endregion

		#region Fill Sub Dirs
		private static void FillSubDirectories(TreeNode tn, ref int imageCount, ImageList imageList, bool getIcons)
		{
			Shell32.FolderItem folderItem = (Shell32.FolderItem)tn.Tag;
			Shell32.Folder folder = (Shell32.Folder)folderItem.GetFolder;

			foreach(Shell32.FolderItem item in folder.Items())
			{
				if(item.IsFileSystem && item.IsFolder && !item.IsBrowsable)
				{
					TreeNode ntn = AddTreeNode(item, ref imageCount, imageList, getIcons);
					tn.Nodes.Add(ntn);
					CheckForSubDirs(ntn, imageList);
				}
			}
		}
		#endregion

		#region Create Dummy Node
		private static void CheckForSubDirs(TreeNode tn, ImageList imageList)
		{
			if(tn.Nodes.Count == 0)
			{
				try
				{
					// create dummy nodes for any subfolders that have further subfolders
					Shell32.FolderItem folderItem = (Shell32.FolderItem)tn.Tag;
					Shell32.Folder folder = (Shell32.Folder)folderItem.GetFolder;

					bool hasFolders = false;
					foreach(Shell32.FolderItem item in folder.Items())
					{
						if(item.IsFileSystem && item.IsFolder && !item.IsBrowsable)
						{
							hasFolders = true;
							break;
						}
					}
					if(hasFolders)
					{
						TreeNode ntn = new TreeNode();
						ntn.Tag = "DUMMYNODE";
						tn.Nodes.Add(ntn);
					}
				}
				catch {}
			}
		}
		#endregion

		#region Expand Branch
		public static void ExpandBranch(TreeNode tn, ImageList imageList)
		{
			// if there's a dummy node present, clear it and replace with actual contents
			if(tn.Nodes.Count == 1 && tn.Nodes[0].Tag.ToString() == "DUMMYNODE")
			{
				tn.Nodes.Clear();
				Shell32.FolderItem folderItem = (Shell32.FolderItem)tn.Tag;
				Shell32.Folder folder = (Shell32.Folder)folderItem.GetFolder;
				int imageCount = imageList.Images.Count - 1;
				foreach(Shell32.FolderItem item in folder.Items())
				{
					if(item.IsFileSystem && item.IsFolder && !item.IsBrowsable)
					{
						TreeNode ntn = AddTreeNode(item, ref imageCount, imageList, true);
						tn.Nodes.Add(ntn);
						CheckForSubDirs(ntn, imageList);
					}
				}
			}
		}
		#endregion

		#region Add Tree Node
		private static TreeNode AddTreeNode(Shell32.FolderItem item, ref int imageCount, ImageList imageList, bool getIcons)
		{
			TreeNode tn = new TreeNode();
			tn.Text = item.Name;
			tn.Tag = item;

			if(getIcons)
			{
				try
				{
					imageCount++;
					tn.ImageIndex = imageCount;
					imageCount++;
					tn.SelectedImageIndex = imageCount;
					imageList.Images.Add(ExtractIcons.GetIcon(item.Path, false, imageList)); // normal icon
					imageList.Images.Add(ExtractIcons.GetIcon(item.Path, true, imageList)); // selected icon
				}
				catch // use default 
				{
					tn.ImageIndex = 1;
					tn.SelectedImageIndex = 2;
				}
			}
			else // use default
			{
				tn.ImageIndex = 1;
				tn.SelectedImageIndex = 2;
			}
			return tn;
		}

		#endregion

		#endregion
	}

	#endregion

	#region ExtractIcons Class

	public class ExtractIcons
	{
		#region Structs & Enum

		[StructLayout(LayoutKind.Sequential)]
			private struct SHFILEINFO
		{
			public SHFILEINFO(bool b)
			{
				hIcon=IntPtr.Zero;iIcon=0;dwAttributes=0;szDisplayName="";szTypeName="";
			}
			public IntPtr hIcon;
			public int iIcon;
			public uint dwAttributes;
			[MarshalAs(UnmanagedType.LPStr, SizeConst=260)]
			public string szDisplayName;
			[MarshalAs(UnmanagedType.LPStr, SizeConst=80)]
			public string szTypeName;
		};

		private enum SHGFI
		{
			SHGFI_ICON =             0x000000100,     // get icon
			SHGFI_DISPLAYNAME =      0x000000200,     // get display name
			SHGFI_TYPENAME =         0x000000400,     // get type name
			SHGFI_ATTRIBUTES =       0x000000800,     // get attributes
			SHGFI_ICONLOCATION =     0x000001000,     // get icon location
			SHGFI_EXETYPE =          0x000002000,     // return exe type
			SHGFI_SYSICONINDEX =     0x000004000,     // get system icon index
			SHGFI_LINKOVERLAY =      0x000008000,     // put a link overlay on icon
			SHGFI_SELECTED =         0x000010000,     // show icon in selected state
			SHGFI_ATTR_SPECIFIED =   0x000020000,     // get only specified attributes
			SHGFI_LARGEICON =        0x000000000,     // get large icon
			SHGFI_SMALLICON =        0x000000001,     // get small icon
			SHGFI_OPENICON =         0x000000002,     // get open icon
			SHGFI_SHELLICONSIZE =    0x000000004,     // get shell size icon
			SHGFI_PIDL =             0x000000008,     // pszPath is a pidl
			SHGFI_USEFILEATTRIBUTES = 0x000000010     // use passed dwFileAttribute
		}

		#endregion

		#region Get Folder Icons

		[DllImport("Shell32.dll")]
		private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, 
			out SHFILEINFO psfi, uint cbfileInfo, SHGFI uFlags );

		public static Icon GetIcon(string strPath, bool selected, ImageList imageList)
		{
			SHFILEINFO info = new SHFILEINFO(true);
			int cbFileInfo = Marshal.SizeOf(info);
			SHGFI flags;
			if (!selected)
				flags = SHGFI.SHGFI_ICON|SHGFI.SHGFI_SMALLICON;
			else
				flags = SHGFI.SHGFI_ICON|SHGFI.SHGFI_SMALLICON|SHGFI.SHGFI_OPENICON;

			SHGetFileInfo(strPath, 256, out info,(uint)cbFileInfo, flags);
			return Icon.FromHandle(info.hIcon);
		}

		#endregion

		#region Get Desktop Icon

		// Retreive the desktop icon from Shell32.dll - it always appears at index 34 in all shell32 versions.
		// This is probably NOT the best way to retreive this icon, but it works - if you have a better way
		// by all means let me know..

//		[DllImport("Shell32.dll", CharSet=CharSet.Auto)]
//		public static extern IntPtr ExtractIcon(int hInst, string lpszExeFileName, int nIconIndex);
//
//		public static Icon GetDesktopIcon()
//		{
//			IntPtr i = ExtractIcon(0, Environment.SystemDirectory + "\\shell32.dll", 34);
//			return Icon.FromHandle(i);
//		}

		// Updated this method in v1.11 so that the icon returned is a small icon, not a large icon as
		// returned by the old method above

		[DllImport("Shell32.dll", CharSet=CharSet.Auto)]
		public static extern uint ExtractIconEx(
			string lpszFile, int nIconIndex, IntPtr[] phiconLarge, IntPtr[] phiconSmall, uint nIcons );

		public static Icon GetDesktopIcon()
		{
			IntPtr[] handlesIconLarge = new IntPtr[1];
			IntPtr[] handlesIconSmall = new IntPtr[1];
			uint i = ExtractIconEx(Environment.SystemDirectory + "\\shell32.dll", 34, 
				handlesIconLarge, handlesIconSmall, 1);

			return Icon.FromHandle(handlesIconSmall[0]);
		}
		
		#endregion

	}

	#endregion


}

